package com.franklin.ideaplugin.easytesting.spring.invoke;


import cn.hutool.core.collection.CollectionUtil;
import com.franklin.ideaplugin.easytesting.common.constants.EasyTestingInnerHeaders;
import com.franklin.ideaplugin.easytesting.common.entity.ETRsp;
import com.franklin.ideaplugin.easytesting.common.entity.MethodInvokeData;
import com.franklin.ideaplugin.easytesting.common.utils.JsonUtils;
import com.franklin.ideaplugin.easytesting.common.utils.MethodUtils;
import com.franklin.ideaplugin.easytesting.controllerclient.utils.ControllerUtil;
import com.franklin.ideaplugin.easytesting.common.constants.EasyTestingHeaders;
import com.franklin.ideaplugin.easytesting.core.invoke.IMethodInvoker;
import com.franklin.ideaplugin.easytesting.core.invoke.InstanceMethodInvoker;
import com.franklin.ideaplugin.easytesting.core.invoke.StaticMethodInvoker;
import com.franklin.ideaplugin.easytesting.common.log.ILogger;
import com.franklin.ideaplugin.easytesting.common.log.LoggerFactory;
import com.franklin.ideaplugin.easytesting.core.rpc.NettyServer;
import com.franklin.ideaplugin.easytesting.spring.config.MyServerProperties;
import com.franklin.ideaplugin.easytesting.spring.constants.StrPool;
import com.franklin.ideaplugin.easytesting.spring.utils.SpringUtil;
import lombok.RequiredArgsConstructor;
import okhttp3.*;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.BeansException;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @author Ye Junhui
 * @since 2023/5/15
 */
@RequiredArgsConstructor
public class SpringMethodInvoker implements IMethodInvoker {

    private static final ILogger log = LoggerFactory.getLogger(SpringMethodInvoker.class);

    private static final MediaType APPLICATION_JSON = MediaType.parse("application/json; charset=utf-8");

    private final OkHttpClient okHttpClient;


    private final IMethodInvoker staticMethodInvoker;
    private final IMethodInvoker instanceMethodInvoker;
    private final IMethodInvoker controllerClientMethodInvoker;
    private final MyServerProperties myServerProperties;

    public SpringMethodInvoker(OkHttpClient okHttpClient, MyServerProperties myServerProperties) {
        this.myServerProperties = myServerProperties;
        this.staticMethodInvoker = new StaticMethodInvoker();
        this.instanceMethodInvoker = new InstanceMethodInvoker();
        this.controllerClientMethodInvoker = new ControllerClientMethodInvoker(myServerProperties);
        this.okHttpClient = okHttpClient;
    }

    @Override
    public Object invoke(MethodInvokeData methodInvokeData, Class<?> targetClass, Method targetMethod, Class<?>[] paramTypes, Object[] params) throws Exception {
        try {
            if (!isHeaderEmpty(methodInvokeData.getHeaderMap())) {
                //有请求头，发送请求
                if (ControllerUtil.isControllerClass(targetClass)) {
                    String httpRequest = methodInvokeData.getHeaderMap().get(EasyTestingHeaders.IS_HTTP_REQUEST);
                    if (Objects.nonNull(httpRequest)) {
                        return controllerClientMethodInvoker.invoke(methodInvokeData, targetClass, targetMethod, paramTypes, params);
                    }
                }
                return invokeMethodWithHttpHeaders(methodInvokeData);
            }
            if (MethodUtils.isStaticMethod(targetMethod)) {
                return staticMethodInvoker.invoke(methodInvokeData, targetClass, targetMethod, paramTypes, params);
            } else {
                //无请求头直接执行
                Object bean = null;
                bean = SpringUtil.getBean(targetClass);
                return targetMethod.invoke(bean, params);
            }
        } catch (BeansException e) {
            //没有找到bean
            //尝试实例方法
            log.info("Easy-Testing -> spring bean {} not found,try instance method ", targetClass.getName());
            return this.instanceMethodInvoker.invoke(methodInvokeData, targetClass, targetMethod, paramTypes, params);
        } catch (InvocationTargetException e) {
            //执行方法异常
            Throwable cause = ((InvocationTargetException) e).getCause();
            log.error("Easy-Testing -> method execute fail ", cause);
            return cause;
        } catch (Throwable e) {
            log.error("Easy-Testing -> method execute fail ", e);
            return e;
        }
    }

    /**
     * 请求头是否为空
     * @param headerMap
     * @return
     */
    private boolean isHeaderEmpty(Map<String,String> headerMap){
        if (CollectionUtil.isEmpty(headerMap)){
            return true;
        }
        Map<String,String> tempMap = new LinkedHashMap<>(headerMap);
        EasyTestingInnerHeaders.clearEtInnerHeaders(tempMap);
        return tempMap.isEmpty();
    }

    /**
     * 带请求头执行方法
     * @param methodInvokeData
     * @return
     */
    private Object invokeMethodWithHttpHeaders(MethodInvokeData methodInvokeData) {

        Request.Builder builder = new Request.Builder();
        String url = this.myServerProperties.getServerPath() + StrPool.INNER_REQUEST_PATH;
        builder.url(url)
                .post(RequestBody.create(APPLICATION_JSON, JsonUtils.toJSONString(methodInvokeData)));
        LinkedHashMap<String, String> headerMap = methodInvokeData.getHeaderMap();
        if (CollectionUtil.isNotEmpty(headerMap)) {
            headerMap.forEach(builder::addHeader);
        }
        Request request = builder.build();
//        EasyTestingThreadPool.getBizThreadPool().execute(() -> {
//
//        });
        Response response = null;
        try {
            response = okHttpClient.newCall(request).execute();
            if (Objects.nonNull(response)) {
                ResponseBody body = response.body();
                if (Objects.nonNull(body)) {
                    String jsonStr = body.string();
                    ETRsp etRsp = JsonUtils.parseObject(jsonStr, ETRsp.class);
                    return etRsp;
                }
            }
        } catch (IOException e) {
        } finally {
            if (Objects.nonNull(response)) {
                response.close();
            }
        }

        return null;
    }

}
